package sceneObjects;

import utils.*;
import main.Intersection;
import main.Parseable;
import main.Parser;

public class Cylinder extends abstractSceneObject implements sceneObject, Parseable {
    private Point3D start;
    private double radius = 0;
    private Vector direction;
    private double length = 0;

    @Override
    public boolean setParameter(String name, String[] args) {
	if (super.setParameter(name, args)) {
	    return true;
	} else if (name.equals("start")) {
	    if (args.length != 3) {
		System.out.println("bad parameters");
		return false;
	    }
	    start = new Point3D(Parser.stringArrToDoubleArr(args));
	    return true;
	} else if (name.equals("radius")) {
	    if (args.length != 1) {
		System.out.println("bad parameters");
		return false;
	    }
	    radius = Double.parseDouble(args[0]);
	    return true;
	} else if (name.equals("direction")) {
	    if (args.length != 3) {
		System.out.println("bad parameters");
		return false;
	    }
	    direction = new Vector(Parser.stringArrToDoubleArr(args));
	    return true;
	} else if (name.equals("length")) {
	    if (args.length != 1) {
		System.out.println("bad parameters");
		return false;
	    }
	    length = Double.parseDouble(args[0]);
	    return true;
	}

	// We didn't find any parameter
	return false;
    }

    public Cylinder(Point3D p0, Vector direction, double radius, double length) {
	this.start = new Point3D(p0);
	this.radius = radius;
	this.direction = direction;
	this.length = length;

	finalizeObject();
    }

    public Cylinder() {

    }

    public Point3D getParameterization(Point3D p) {
	Vector v1 = direction;

	double distance = Math.sqrt(start.distance(p) * start.distance(p) - radius * radius);

	Vector axis = new Vector(1, 0, 0); // Take the X axis
	if (Math.abs(axis.dotProduct(v1)) == 1) { // Z is actually the X axis, therefore pick Y
	    axis = new Vector(0, 1, 0);
	}
	Vector v2 = axis.crossProduct(v1).normalize();

	Point3D horizontal = start.addVector(direction.scale(distance));
	Vector planeVector = new Vector(horizontal, p).normalize();

	double u = distance / length;
	double v = Math.acos(v2.dotProduct(planeVector));

	Vector normalChecker = planeVector.crossProduct(v2).normalize();
	if (normalChecker.dotProduct(direction) < 0) {
	    v = Math.PI * 2 - v;
	}
	v = v / (2 * Math.PI);

	u = Math.min(u, 0.99999);
	v = Math.min(v, 0.99999);

	return new Point3D(u, v, 0);

    }

    public Point3D getParameterization(Intersection intersect) {
	return getParameterization(intersect.getPoint());
    }

    @Override
    public boolean finalizeObject() {
	if (!super.finalizeObject()) {
	    return false;
	}
	// Checks if the 3 points exists
	if (start == null || radius <= 0 || length <= 0 || direction == null) {
	    System.out.println("Error: one or more parameters are missing");
	    return false;
	}

	direction = direction.normalize();

	return true;
    }

    @Override
    public boolean isCamera() {
	return false;
    }

    public boolean isLight() {
	return false;
    }

    private boolean isPointInside(Point3D p) {
	Vector v = new Vector(start, p);
	double t = direction.dotProduct(v);

	return !(t > length || t < 0);
    }

    @Override
    public Intersection getIntersect(Ray ray) {
	double t1 = 0, t2 = 0, tMin = 0;

	Vector v = new Vector(start, ray.getSource());
	double c1 = ray.getDirection().dotProduct(direction);
	double c2 = direction.dotProduct(v);

	Vector v1 = direction.scale(c1).substract(ray.getDirection());
	double v1Norma = v1.getNorma();
	Vector v2 = direction.scale(c2).substract(v);
	double v2Norma = v2.getNorma();

	double a = (v1Norma * v1Norma);
	double b = 2 * v1.dotProduct(v2);
	double c = ((v2Norma * v2Norma)) - radius * radius;
	double delta = ((b * b) - (4 * a * c));

	if (delta < 0) {
	    return null;
	}

	t1 = (-b + Math.sqrt(delta)) / (2 * a);
	t1 = t1 > 0 && isPointInside(ray.getPointAtDistance(t1)) ? t1 : 0;
	t2 = (-b - Math.sqrt(delta)) / (2 * a);
	t2 = t2 > 0 && isPointInside(ray.getPointAtDistance(t2)) ? t2 : 0;

	if (t1 > 0 && t2 > 0) { // If both are a hit, take the minimum
	    tMin = Math.min(t1, t2);
	} else { // If at most one is a hit, the the maximum (if both aren't, we'll get 0)
	    tMin = Math.max(t1, t2);
	}

	return (tMin == 0 ? null : new Intersection(ray.getPointAtDistance(tMin - 0.0001F), this));
    }

    @Override
    public Vector getNormalAtPoint(Intersection intersect) {
	double distDiag = new Vector(start, intersect.getPoint()).getNorma();
	double height = Math.sqrt(distDiag * distDiag - radius * radius);
	return new Vector(start.addVector(direction.scale(height)), intersect.getPoint()).normalize();
    }

}
